
package FileIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

import Model.Conference;
import Model.Paper;
import Model.User;
import Model.Conference.Role;

/**
 * @author: William Kim
 * @version: Autumn 2012
 * 
 * This class is for managing persistent data for the Conference Wizard program.
 * It is only guaranteed to be accurate at startup. Calling the load_*() methods
 * after startup may not return most current data.
 * 
 * The loadUsers MUST be called before loadConferences!
 * 
 * The user list data and conference list data must be saved at the same time.
 * 
 * List of users will be maintained in the file data/users.txt. Every set of
 * three lines corresponds to one user: Real name, username, and email.
 * 
 * A list of the conference ids will be kept in data/conferences.txt. The
 * conference id will be used as the subdirectory for where the rest of the
 * conference data is stored. The conference id is generated as a hash of the
 * conference object. Similarly, within each conference directory will be
 * subdirectories for each paper. These directories store information relating
 * to each paper object. These directory names are also generated as a hash of
 * the Paper object.
 * 
 * 
 */
public class FileManager
{
  // this directory was chosen in order to leave out the persistent data from
  // SVN. Use "data/" instead if relative directory to the program is desired
  // instead.
  //private static final String DATA_DIR = System.getProperty("user.home")
  //    + "/data/";
  private static final String DATA_DIR = "data/";
  private static final String USER_FILE = "users.txt";
  private static final String CONFERENCE_FILE = "conferences.txt";
  private static final String INFO_FILE = "info.txt";

  private static final Map<String, User> USER_MAP = new HashMap<String, User>();

  /**
   * Loads the user data from file.
   * 
   * @return a list of User objects. If an exception is thrown while attempting
   *         to load the file, this method returns null.
   */
  public static Set<User> loadUsers()
  {
    final Set<User> users_list = new HashSet<User>();

    try
    {
      final Scanner users_scanner = new Scanner(new File(DATA_DIR + USER_FILE));
      while (users_scanner.hasNextLine())
      {
        final User user = new User(users_scanner.nextLine(),
            users_scanner.nextLine(), users_scanner.nextLine());
        users_list.add(user);
        USER_MAP.put(user.userName(), user);
      }
      users_scanner.close();

    } catch (final Exception e)
    {
      System.err.println("Error reading file! " + e.getMessage());
      return null;
    }

    return users_list;
  }

  /**
   * Loads the conference data from file.
   * 
   * @return a list of Conference objects. If an exception is thrown while
   *         attempting to load the file, this method returns null.
   */
  public static List<Conference> loadConferences()
  {
    final List<Conference> conf_list = new ArrayList<Conference>();

    try
    {
      final Scanner conf_scanner = new Scanner(new File(DATA_DIR
          + CONFERENCE_FILE));
      while (conf_scanner.hasNextInt())
      {
        conf_list.add(buildConference(conf_scanner.nextInt()));
      }
      conf_scanner.close();

      return conf_list;
    } catch (final Exception e)
    {
      System.err.println("Error while loading conference data! "
          + e.getMessage());
      return null;
    }

  }

  /**
   * Helper method to build up a Conference object from the saved data.
   * 
   * @param the_conference_id
   *          the conference id
   * @return a Conference object
   */
  @SuppressWarnings("deprecation")
  private static Conference buildConference(final int the_conference_id)
  {
    try
    {
      final String dir = DATA_DIR + the_conference_id + "/";

      Scanner info_scanner = new Scanner(new File(dir + INFO_FILE));
      final String name = info_scanner.nextLine();
      final User pc = USER_MAP.get(info_scanner.nextLine());
      final Date deadline = new Date(info_scanner.nextInt(),
          info_scanner.nextInt(), info_scanner.nextInt());
      info_scanner.close();
      final Map<User, List<Role>> roles_map = new HashMap<User, List<Role>>();
      Scanner roles_scanner = new Scanner(new File(dir + "roles.txt"));
      while (roles_scanner.hasNextLine())
      {
        final User user = USER_MAP.get(roles_scanner.nextLine());
        final List<Role> roles = new ArrayList<Role>();
        final Scanner role_lines = new Scanner(roles_scanner.nextLine());
        while (role_lines.hasNext())
        {
          final String role = role_lines.next();
          roles.add(Role.valueOf(role));
        }
        roles_map.put(user, roles);
        role_lines.close();
      }
      roles_scanner.close();

      Conference conference = new Conference(name, pc, roles_map, deadline);

      Scanner paper_scanner = new Scanner(new File(dir + "papers.txt"));
      while (paper_scanner.hasNextInt())
      {
        int paper_hash = paper_scanner.nextInt();
        conference.addPaper(buildPaper(the_conference_id, paper_hash));
      }
      paper_scanner.close();

      return conference;
    } catch (final Exception e)
    {
      final Writer result = new StringWriter();
      final PrintWriter printWriter = new PrintWriter(result);
      e.printStackTrace(printWriter);
      System.err.println("Error while building conference! " + result);
      return null;
    }
  }

  /*
   * Helper method to build up paper objects from data in files. All paper data
   * will be stored in a directory named after the hashCode returned by the
   * Paper, under the conference directory it is part of.
   */
  private static Paper buildPaper(final int the_conference_id,
      final int the_paper_hash)
  {
    try
    {
      final Paper paper;

      final String dir = DATA_DIR + the_conference_id + "/" + the_paper_hash
          + "/";

      // build in title, author, manuscript file
      // (and Subchair, recommendations only if they exist)
      final Scanner paper_info = new Scanner(new File(dir + "info.txt"));

      final String title = paper_info.nextLine();
      final User author = USER_MAP.get(paper_info.nextLine());
      final File manuscript = new File(dir + paper_info.nextLine());
      final int decision = Integer.parseInt(paper_info.nextLine());

      if (paper_info.hasNextLine())
      {
        final User spc = USER_MAP.get(paper_info.nextLine());
        paper = new Paper(title, author, spc, manuscript);

        if (paper_info.hasNextLine())
        {
          final File recommendation = new File(dir + paper_info.nextLine());
          paper.editRecommendation(recommendation);
        }
      } else
      {
        paper = new Paper(title, author, manuscript);
      }
      paper.editDecision(decision);
      paper_info.close();

      // build in reviews map
      final File reviews = new File(dir + "reviews.txt");
      if (reviews.exists())
      {
        final Scanner reviews_scanner = new Scanner(reviews);
        while (reviews_scanner.hasNextLine())
        {
          final User reviewer = USER_MAP.get(reviews_scanner.nextLine());
          final String review = reviews_scanner.nextLine();
          File review_file = null;
          if (!review.equals("."))
          {
            review_file = new File(dir + review);
          }
          paper.editReview(reviewer, review_file);
        }
        reviews_scanner.close();
      }

      return paper;
    } catch (final Exception e)
    {
      System.err.println("Error while building paper! " + e.getMessage());
      return null;
    }
  }

  /**
   * Saves the user data to file. Not guaranteed to be robust under exceptions.
   */
  public static void saveUsers(final Set<User> the_users)
  {
    try
    {
      final File dir = new File(DATA_DIR);
      if (!dir.exists())
      {
        dir.mkdir();
      }
      final PrintStream ps = new PrintStream(new File(DATA_DIR + USER_FILE));
      for (User u : the_users)
      {
        ps.println(u.realName());
        ps.println(u.userName());
        ps.println(u.email());
      }
      ps.close();
    } catch (final Exception e)
    {
      System.err.println("Error while saving users data! " + e.getMessage());
    }
  }

  /**
   * Saves the conference data to file.
   */
  @SuppressWarnings("deprecation")
  public static void saveConferences(final List<Conference> the_conferences)
  {
    try
    {
      final File dir = new File(DATA_DIR);
      if (!dir.exists())
      {
        dir.mkdir();
      }
      final PrintStream list_printer = new PrintStream(new File(DATA_DIR
          + CONFERENCE_FILE));
      for (Conference c : the_conferences)
      {
        list_printer.println(c.hashCode());
        final String conference_dir_path = DATA_DIR + c.hashCode() + "/";
        final File conference_dir = new File(conference_dir_path);
        conference_dir.mkdir();

        // print the general conference info to file
        final PrintStream info_printer = new PrintStream(new File(
            conference_dir_path + INFO_FILE));
        info_printer.println(c.conferenceName());
        info_printer.println(c.programChair().userName());
        info_printer.print(c.getDeadline().getYear() + " ");
        info_printer.print(c.getDeadline().getMonth() + " ");
        info_printer.println(c.getDeadline().getDate());
        info_printer.close();

        final PrintStream roles_printer = new PrintStream(new File(
            conference_dir_path + "roles.txt"));

        final Map<User, List<Role>> role_map = c.userMappings();

        for (User user : role_map.keySet())
        {
          roles_printer.println(user.userName());
          for (Role role : role_map.get(user))
          {
            roles_printer.print(role.toString() + " ");
          }
          roles_printer.println();
        }
        roles_printer.close();

        // Saving paper info
        final PrintStream papers_printer = new PrintStream(new File(
            conference_dir_path + "papers.txt"));
        for (Paper paper : c.getSubmittedPapers())
        {
          papers_printer.println(paper.hashCode());
          savePaper(paper, conference_dir_path);
        }
        papers_printer.close();

      }
      list_printer.close();

    } catch (final Exception e)
    {
      System.err.println("Error while saving conference data! "
          + e.getMessage());
    }
  }

  // Helper method for saving paper data.
  private static void savePaper(final Paper paper,
      final String conference_dir_path)
  {
    try
    {

      final String paper_path = conference_dir_path + paper.hashCode() + "/";
      (new File(paper_path)).mkdir();

      // general info
      final PrintStream info_printer = new PrintStream(new File(paper_path
          + INFO_FILE));
      info_printer.println(paper.getTitle());
      info_printer.println(paper.getAuthor().userName());
      info_printer.println(paper.getManuscript().getName());
      saveFile(paper.getManuscript(), paper_path
          + paper.getManuscript().getName());
      info_printer.println("" + paper.getDecision());
      if (paper.getSubprogramChair() != null)
      {
        info_printer.println(paper.getSubprogramChair().userName());
        if (paper.getRecommendation() != null)
        {
          info_printer.println(paper.getRecommendation().getName());
          saveFile(paper.getRecommendation(), paper_path
              + paper.getRecommendation().getName());
        }
      }
      info_printer.close();

      // reviewers and reviews
      final PrintStream review_printer = new PrintStream(new File(paper_path
          + "reviews.txt"));
      for (User reviewer : paper.getMyReviewAssociation().keySet())
      {
        review_printer.println(reviewer.userName());
        final File review = paper.getMyReviewAssociation().get(reviewer);
        if (review != null)
        {
          review_printer.println(review.getName());
          saveFile(review, paper_path + review.getName());
        } else
        {
          review_printer.println(".");
        }
      }
      review_printer.close();

    } catch (final Exception e)
    {
      System.err.println("Error while saving paper data! " + e.getMessage());
    }
  }

  private static void saveFile(final File original,
      final String saved_file_location) throws IOException
  {
    FileInputStream in = null;
    FileOutputStream out = null;

    try
    {
      in = new FileInputStream(original);
      out = new FileOutputStream(saved_file_location);
      int c;

      while ((c = in.read()) != -1)
      {
        out.write(c);
      }
    } finally
    {
      if (in != null)
      {
        in.close();
      }
      if (out != null)
      {
        out.close();
      }
    }
  }

}
